<?php
/**
 * ---------------------------------------------------------------------
 * GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2015-2018 Teclib' and contributors.
 *
 * http://glpi-project.org
 *
 * based on GLPI - Gestionnaire Libre de Parc Informatique
 * Copyright (C) 2003-2014 by the INDEPNET Development Team.
 *
 * ---------------------------------------------------------------------
 *
 * LICENSE
 *
 * This file is part of GLPI.
 *
 * GLPI is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * GLPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GLPI. If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------
*/

namespace tests\units;

/* Test for inc/toolbox.class.php */

class Toolbox extends \GLPITestCase {

   public function testGetRandomString() {
      for ($len = 20; $len < 50; $len += 5) {
         // Low strength
         $str = \Toolbox::getRandomString($len);
         $this->integer(strlen($str))->isIdenticalTo($len);
         $this->boolean(ctype_alnum($str))->isTrue();
      }
   }

   public function testRemoveHtmlSpecialChars() {
      $original = 'My - string èé  Ê À ß';
      $expected = 'my - string ee  e a sz';
      $result = \Toolbox::removeHtmlSpecialChars($original);

      $this->string($result)->isIdenticalTo($expected);
   }

   protected function slugifyProvider() {
      return [
         [
            'string'    => 'My - string èé  Ê À ß',
            'expcected' => 'my-string-ee-e-a-sz'
         ], [
            //https://github.com/glpi-project/glpi/issues/2946
            'string'    => 'Έρευνα ικανοποίησης - Αιτήματα',
            'expcected' => 'ereuna-ikanopoieses-aitemata'
         ]
      ];
   }

   /**
    * @dataProvider slugifyProvider
    */
   public function testSlugify($string, $expected) {
      $this->string(\Toolbox::slugify($string))->isIdenticalTo($expected);
   }

   public function dataGetSize() {
      return [
         [1,                   '1 o'],
         [1025,                '1 Kio'],
         [1100000,             '1.05 Mio'],
         [1100000000,          '1.02 Gio'],
         [1100000000000,       '1 Tio'],
      ];
   }

   /**
    * @dataProvider dataGetSize
    */
   public function testGetSize($input, $expected) {
      $this->string(\Toolbox::getSize($input))->isIdenticalTo($expected);
   }

   public function testGetIPAddress() {
      // Save values
      $saveServer = $_SERVER;

      // Test REMOTE_ADDR
      unset($_SERVER['HTTP_X_FORWARDED_FOR']);
      $_SERVER['REMOTE_ADDR'] = '123.123.123.123';
      $ip = \Toolbox::getRemoteIpAddress();
      $this->variable($ip)->isEqualTo('123.123.123.123');

      // Test HTTP_X_FORWARDED_FOR takes precedence over REMOTE_ADDR
      $_SERVER['HTTP_X_FORWARDED_FOR'] = '231.231.231.231';
      $ip = \Toolbox::getRemoteIpAddress();
      $this->variable($ip)->isEqualTo('231.231.231.231');

      // Restore values
      $_SERVER = $saveServer;
   }

   public function testFormatOutputWebLink() {
      $this->string(\Toolbox::formatOutputWebLink('www.glpi-project.org/'))
         ->isIdenticalTo('http://www.glpi-project.org/');
      $this->string(\Toolbox::formatOutputWebLink('http://www.glpi-project.org/'))
         ->isIdenticalTo('http://www.glpi-project.org/');
      $this->string(\Toolbox::formatOutputWebLink('https://www.glpi-project.org/'))
         ->isIdenticalTo('https://www.glpi-project.org/');
   }

   public function testUncleanHtmlCrossSideScriptingDeepImageBase64() {
      $image_base_64 = html_entity_decode('&lt;p&gt; &lt;img id="c4b1c031-4c85d722-5a3136eacbe3a9.88695094" src="" width="251" height="245" /&gt;&lt;/p&gt;&lt;p&gt; &lt;/p&gt;');

      $this->string(\Toolbox::unclean_html_cross_side_scripting_deep($image_base_64))
         ->notContains('denied:');
   }

   public function testgetBijectiveIndex() {
      foreach ([
         1   => 'A',
         2   => 'B',
         27  => 'AA',
         28  => 'AB',
         53  => 'BA',
         702 => 'ZZ',
         703 => 'AAA',
      ] as $number => $bij_string) {
         $this->string(\Toolbox::getBijectiveIndex($number))->isIdenticalTo($bij_string);
      }
   }

   protected function cleanIntegerProvider() {
      return [
         [1, '1'],
         ['1', '1'],
         ['a1', '1'],
         ['-1', '-1'],
         ['-a1', '-1'],
      ];
   }

   /**
    * @dataProvider cleanIntegerProvider
    */
   public function testCleanInteger($value, $expected) {
      $this->variable(\Toolbox::cleanInteger($value))->isIdenticalTo($expected);
   }

   protected function jsonDecodeProvider() {
      return [
         [
            '{"Monitor":[6],"Computer":[35]}',
            ['Monitor' => [6], 'Computer' => [35]]
         ], [
            '{\"Monitor\":[\"6\"],\"Computer\":[\"35\"]}',
            ['Monitor' => ["6"], 'Computer' => ["35"]]
         ]
      ];
   }

   /**
    * @dataProvider jsonDecodeProvider
    */
   public function testJsonDecode($json, $expected) {
      $this
         ->variable(\Toolbox::jsonDecode($json, true))
         ->isIdenticalTo($expected);
   }

   public function testJsonDecodeWException() {
      $this->exception(
         function() {
            $this
               ->variable(\Toolbox::jsonDecode('"Monitor":"6","Computer":"35"', true));
         }
      )
         ->isInstanceOf('RuntimeException')
         ->message->contains('Unable to decode JSON string! Is this really JSON?');
   }

   protected function ucProvider() {
      return [
         ['hello you', 'Hello you'],
         ['HEllO you', 'HEllO you'],
         ['éè', 'Éè'],
         ['ÉÈ', 'ÉÈ']
      ];
   }

   /**
    * @dataProvider ucProvider
    */
   public function testUcfirst($in, $out) {
      $this->string(\Toolbox::ucfirst($in))->isIdenticalTo($out);
   }

   protected function shortcutProvider() {
      return [
         ['My menu', 'm', '<u>M</u>y menu'],
         ['Do something', 't', 'Do some<u>t</u>hing'],
         ['Any menu entry', 'z', 'Any menu entry'],
         ['Computer', 'O', 'C<u>o</u>mputer']
      ];
   }

   /**
    * @dataProvider shortcutProvider
    */
   public function testShortcut($string, $letter, $expected) {
      $this->string(\Toolbox::shortcut($string, $letter))->isIdenticalTo($expected);
   }

   protected function strposProvider() {
      return [
         ['Where is Charlie?', 'W', 0, 0],
         ['Where is Charlie?', 'W', 1, false],
         ['Where is Charlie?', 'w', 0, false],
         ['Where is Charlie?', '?', 0, 16],
         ['Where is Charlie?', '?', 3, 16],
         ['Where is Charlie?', 'e', 0, 2],
         ['Where is Charlie?', 'e', 2, 2],
         ['Where is Charlie?', 'e', 3, 4],
         ['Où est Charlie ?', 'ù', 0, 1]
      ];
   }

   /**
    * @dataProvider strposProvider
    */
   public function testStrpos($string, $search, $offset, $expected) {
      $this->variable(\Toolbox::strpos($string, $search, $offset))->isIdenticalTo($expected);
   }

   protected function padProvider() {
      return [
         ['GLPI', 10, " ", STR_PAD_RIGHT, 'GLPI      '],
         ['éè', 10, " ", STR_PAD_RIGHT, 'éè        '],
         ['GLPI', 10, " ", STR_PAD_LEFT, '      GLPI'],
         ['éè', 10, " ", STR_PAD_LEFT, '        éè'],
         ['GLPI', 10, " ", STR_PAD_BOTH, '   GLPI   '],
         ['éè', 10, " ", STR_PAD_BOTH, '    éè    '],
         ['123', 10, " ", STR_PAD_BOTH, '   123    ']
      ];
   }

   /**
    * @dataProvider padProvider
    */
   public function testStr_pad($string, $length, $char, $pad, $expected) {
      $this->string(\Toolbox::str_pad($string, $length, $char, $pad))
         ->isIdenticalTo($expected);
   }

   protected function strlenProvider() {
      return [
         ['GLPI', 4],
         ['Où ça ?', 7]
      ];
   }

   /**
    * @dataProvider strlenProvider
    */
   public function testStrlen($string, $length) {
      $this->integer(\Toolbox::strlen($string))->isIdenticalTo($length);
   }

   protected function substrProvider() {
      return [
         ['I want a substring', 0, -1, 'I want a substring'],
         ['I want a substring', 9, -1, 'substring'],
         ['I want a substring', 9, 3, 'sub'],
         ['Caractères accentués', 0, -1, 'Caractères accentués'],
         ['Caractères accentués', 11, -1, 'accentués'],
         ['Caractères accentués', 11, 8, 'accentué']
      ];
   }

   /**
    * @dataProvider substrProvider
    */
   public function testSubstr($string, $start, $length, $expected) {
      $this->string(\Toolbox::substr($string, $start, $length))
         ->isIdenticalTo($expected);
   }

   protected function lowercaseProvider() {
      return [
         ['GLPI', 'glpi'],
         ['ÉÈ', 'éè'],
         ['glpi', 'glpi']
      ];
   }

   /**
    * @dataProvider lowercaseProvider
    */
   public function testStrtolower($upper, $lower) {
      $this->string(\Toolbox::strtolower($upper))->isIdenticalTo($lower);
   }

   protected function uppercaseProvider() {
      return [
         ['glpi', 'GLPI'],
         ['éè', 'ÉÈ'],
         ['GlPI', 'GLPI']
      ];
   }

   /**
    * @dataProvider uppercaseProvider
    */
   public function testStrtoupper($lower, $upper) {
      $this->string(\Toolbox::strtoupper($lower))->isIdenticalTo($upper);
   }

   protected function utfProvider() {
      return [
         ['a simple string', true],
         ['caractère', true],
         [mb_convert_encoding('caractère', 'ISO-8859-15'), false],
         [mb_convert_encoding('simple string', 'ISO-8859-15'), true]
      ];
   }

   /**
    * @dataProvider utfProvider
    */
   public function testSeems_utf8($string, $utf) {
      $this->boolean(\Toolbox::seems_utf8($string))->isIdenticalTo($utf);
   }

   protected function encryptProvider() {
      return [
         ['My string', 'mykey', 'xuaZ3tnr1ufS'],
         ['keepmysecret', 'keepmykey', '5NDK1d3m7NDI69DZ']
      ];
   }

   /**
    * @dataProvider encryptProvider
    */
   public function testEncrypt($string, $key, $expected) {
      $this->string(\Toolbox::encrypt($string, $key))->isIdenticalTo($expected);
   }

   /**
    * @dataProvider encryptProvider
    */
   public function testDecrypt($expected, $key, $string) {
      $this->string(\Toolbox::decrypt($string, $key))->isIdenticalTo($expected);
   }

   protected function cleanProvider() {
      return [
         ['mystring', 'mystring', null, 15, 0.56, false],
         ['<strong>string</strong>', '&lt;strong&gt;string&lt;/strong&gt;', null, 15, 0.56, false],
         [
            [null, '<strong>string</strong>', 3.2, 'string', true, '<p>my</p>', 9798],
            [null, '&lt;strong&gt;string&lt;/strong&gt;', 3.2, 'string', true, '&lt;p&gt;my&lt;/p&gt;', 9798]
         ]
      ];
   }

   /**
    * @dataProvider cleanProvider
    */
   public function testClean_cross_side_scripting_deep($value, $expected) {
      $this->variable(\Toolbox::clean_cross_side_scripting_deep($value))
         ->isIdenticalTo($expected);
   }

   /**
    * @dataProvider cleanProvider
    */
   public function testUnclean_cross_side_scripting_deep($expected, $value) {
      $this->variable(\Toolbox::unclean_cross_side_scripting_deep($value))
         ->isIdenticalTo($expected);
   }

   protected function cleanHtmlProvider() {
      $dataset = $this->cleanProvider();
      $dataset[] = [
         ['<div>Here a list example: <pre>&lt;ul&gt;&lt;li&gt;one&lt;/li&gt;&lt;li&gt;two&lt;/li&gt;&lt;/ul&gt;</pre></div>'],
         ['&lt;div&gt;Here a list example: &lt;pre&gt;&lt;ul&gt;&lt;li&gt;one&lt;/li&gt;&lt;li&gt;two&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt;']
      ];
      return $dataset;
   }

   /**
    * @dataProvider cleanHtmlProvider
    */
   public function testUnclean_html_cross_side_scripting_deep($expected, $value) {
      $this->variable(\Toolbox::unclean_html_cross_side_scripting_deep($value))
         ->isIdenticalTo($expected);
   }

   public function testSaveAndDeletePicture() {
      // Save an image twice
      $test_file = __DIR__ . '/../files/test.png';
      copy(__DIR__ . '/../../pics/add_dropdown.png', $test_file); // saved image will be removed from FS
      $first_pict = \Toolbox::savePicture($test_file);
      $this->string($first_pict)->matches('#[^/]+/.+\.png#'); // generated random name inside subdir

      copy(__DIR__ . '/../../pics/add_dropdown.png', $test_file); // saved image will be removed from FS
      $second_pict = \Toolbox::savePicture($test_file);
      $this->string($second_pict)->matches('#[^/]+/.+\.png#'); // generated random name inside subdir

      // Check that second saving of same image is not overriding first saved image.
      $this->string($first_pict)->isNotEqualTo($second_pict);

      // Delete saved images
      $this->boolean(\Toolbox::deletePicture($first_pict))->isTrue();
      $this->boolean(\Toolbox::deletePicture($second_pict))->isTrue();

      // Save not an image
      $this->boolean(\Toolbox::savePicture(__DIR__ . '/../notanimage.jpg'))->isFalse();

      // Save and delete unexisting files
      $this->boolean(\Toolbox::savePicture('notafile.jpg'))->isFalse();
      $this->boolean(\Toolbox::deletePicture('notafile.jpg'))->isFalse();
   }

   protected function getPictureUrlProvider() {
      global $CFG_GLPI;

      return [
         [
            'path' => '',
            'url'  => null,
         ],
         [
            'path' => 'image.jpg',
            'url'  => $CFG_GLPI['root_doc'] . '/front/document.send.php?file=_pictures/image.jpg',
         ],
         [
            'path' => 'xss\' onclick="alert(\'PWNED\')".jpg',
            'url'  => $CFG_GLPI['root_doc'] . '/front/document.send.php?file=_pictures/xss&apos; onclick=&quot;alert(&apos;PWNED&apos;)&quot;.jpg',
         ],
      ];
   }

   /**
    * @dataProvider getPictureUrlProvider
    */
   public function testGetPictureUrl($path, $url) {
      $this->variable(\Toolbox::getPictureUrl($path))->isIdenticalTo($url);
   }

   /**
    * Data provider for self::testConvertTagToImage().
    */
   protected function convertTagToImageProvider() {
      $data = [];

      foreach ([\Computer::class, \Change::class, \Problem::class, \Ticket::class] as $itemtype) {
         $item = new $itemtype();
         $item->fields['id'] = mt_rand(1, 50);

         $img_url = '/front/document.send.php?docid={docid}'; //{docid} to replace by generated doc id
         if ($item instanceof \CommonITILObject) {
            $img_url .= '&' . $item->getForeignKeyField() . '=' . $item->fields['id'];
         }

         $data[] = [
            'item'         => $item,
            'expected_url' => $img_url,
         ];

         if ($item instanceof \CommonITILObject) {
            $fup = new \ITILFollowup();
            $fup->input['_job'] = $item;
            $data[] = [
               'item'         => $fup,
               'expected_url' => $img_url,
            ];

            $solution = new \ITILSolution();
            $solution->input['_job'] = $item;
            $data[] = [
               'item'         => $solution,
               'expected_url' => $img_url,
            ];

            $task_itemtype = $itemtype . 'Task';
            $task = new $task_itemtype();
            $task->input['_job'] = $item;
            $data[] = [
               'item'         => $task,
               'expected_url' => $img_url,
            ];
         }
      }

      return $data;
   }

   /**
    * Check conversion of tags to images.
    *
    * @dataProvider convertTagToImageProvider
    */
   public function testConvertTagToImage($item, $expected_url) {

      $img_tag = uniqid('', true);

      // Create document in DB
      $document = new \Document();
      $doc_id = $document->add([
         'name'     => 'basic document',
         'filename' => 'img.png',
         'mime'     => 'image/png',
         'tag'      => $img_tag,
      ]);
      $this->integer((int)$doc_id)->isGreaterThan(0);

      $content_text   = '<img id="' . $img_tag. '" width="10" height="10" />';
      $expected_url   = str_replace('{docid}', $doc_id, $expected_url);
      $expected_result = '<a href="' . $expected_url . '" target="_blank" ><img alt="' . $img_tag. '" width="10" src="' . $expected_url. '" /></a>';

      // Processed data is expected to be escaped
      $content_text = \Toolbox::addslashes_deep($content_text);
      $expected_result = \Html::entities_deep($expected_result);

      $this->string(
         \Toolbox::convertTagToImage($content_text, $item, [$doc_id => ['tag' => $img_tag]])
      )->isEqualTo($expected_result);
   }

   /**
    * Data provider for self::testBaseUrlInConvertTagToImage().
    */
   protected function convertTagToImageBaseUrlProvider() {
      $item = new \Ticket();
      $item->fields['id'] = mt_rand(1, 50);

      $img_url = '/front/document.send.php?docid={docid}'; //{docid} to replace by generated doc id
      $img_url .= '&tickets_id=' . $item->fields['id'];

      return [
         [
            'url_base'     => 'http://glpi.domain.org',
            'item'         => $item,
            'expected_url' => $img_url,
         ],
         [
            'url_base'     => 'http://www.domain.org/glpi/v9.4/',
            'item'         => $item,
            'expected_url' => '/glpi/v9.4/' . $img_url,
         ],
      ];
   }

   /**
    * Check base url handling in conversion of tags to images.
    *
    * @dataProvider convertTagToImageBaseUrlProvider
    */
   public function testBaseUrlInConvertTagToImage($url_base, $item, $expected_url) {

      $img_tag = uniqid('', true);

      // Create document in DB
      $document = new \Document();
      $doc_id = $document->add([
         'name'     => 'basic document',
         'filename' => 'img.png',
         'mime'     => 'image/png',
         'tag'      => $img_tag,
      ]);
      $this->integer((int)$doc_id)->isGreaterThan(0);

      $content_text   = '<img id="' . $img_tag. '" width="10" height="10" />';
      $expected_url   = str_replace('{docid}', $doc_id, $expected_url);
      $expected_result = '<a href="' . $expected_url . '" target="_blank" ><img alt="' . $img_tag. '" width="10" src="' . $expected_url. '" /></a>';

      // Processed data is expected to be escaped
      $content_text = \Toolbox::addslashes_deep($content_text);
      $expected_result = \Html::entities_deep($expected_result);

      // Save old config
      global $CFG_GLPI;
      $old_url_base = $CFG_GLPI['url_base'];

      // Get result
      $CFG_GLPI['url_base'] = $url_base;
      $result = \Toolbox::convertTagToImage($content_text, $item, [$doc_id => ['tag' => $img_tag]]);

      // Restore config
      $CFG_GLPI['url_base'] = $old_url_base;

      // Validate result
      $this->string($result)->isEqualTo($expected_result);
   }

   /**
    * Check conversion of tags to images when contents contains multiple inlined images.
    */
   public function testConvertTagToImageWithMultipleInlinedImg() {

      $img_tag_1 = uniqid('', true);
      $img_tag_2 = uniqid('', true);
      $img_tag_3 = uniqid('', true);

      $item = new \Ticket();
      $item->fields['id'] = mt_rand(1, 50);

      // Create multiple documents in DB
      $document = new \Document();
      $doc_id_1 = $document->add([
         'name'     => 'document 1',
         'filename' => 'img1.png',
         'mime'     => 'image/png',
         'tag'      => $img_tag_1,
      ]);
      $this->integer((int)$doc_id_1)->isGreaterThan(0);

      $document = new \Document();
      $doc_id_2 = $document->add([
         'name'     => 'document 2',
         'filename' => 'img2.png',
         'mime'     => 'image/png',
         'tag'      => $img_tag_2,
      ]);
      $this->integer((int)$doc_id_2)->isGreaterThan(0);

      $document = new \Document();
      $doc_id_3 = $document->add([
         'name'     => 'document 3',
         'filename' => 'img3.png',
         'mime'     => 'image/png',
         'tag'      => $img_tag_3,
      ]);
      $this->integer((int)$doc_id_3)->isGreaterThan(0);

      $doc_data = [
         $doc_id_1 => ['tag' => $img_tag_1],
         $doc_id_2 => ['tag' => $img_tag_2],
         $doc_id_3 => ['tag' => $img_tag_3],
      ];

      $content_text    = '';
      $expected_result = '';
      foreach ($doc_data as $doc_id => $doc) {
         $expected_url    = '/front/document.send.php?docid=' . $doc_id . '&tickets_id=' . $item->fields['id'];
         $content_text    .= '<img id="' . $doc['tag'] . '" width="10" height="10" />';
         $expected_result .= '<a href="' . $expected_url . '" target="_blank" ><img alt="' . $doc['tag'] . '" width="10" src="' . $expected_url . '" /></a>';
      }

      // Processed data is expected to be escaped
      $content_text = \Toolbox::addslashes_deep($content_text);
      $expected_result = \Html::entities_deep($expected_result);

      $this->string(
         \Toolbox::convertTagToImage($content_text, $item, $doc_data)
      )->isEqualTo($expected_result);
   }

   /**
    * Check conversion of tags to images when multiple document matches same tag.
    */
   public function testConvertTagToImageWithMultipleDocMatchesSameTag() {

      $img_tag = uniqid('', true);

      $item = new \Ticket();
      $item->fields['id'] = mt_rand(1, 50);

      // Create multiple documents in DB
      $document = new \Document();
      $doc_id_1 = $document->add([
         'name'     => 'duplicated document 1',
         'filename' => 'img.png',
         'mime'     => 'image/png',
         'tag'      => $img_tag,
      ]);
      $this->integer((int)$doc_id_1)->isGreaterThan(0);

      $document = new \Document();
      $doc_id_2 = $document->add([
         'name'     => 'duplicated document 2',
         'filename' => 'img.png',
         'mime'     => 'image/png',
         'tag'      => $img_tag,
      ]);
      $this->integer((int)$doc_id_2)->isGreaterThan(0);

      $content_text    = '<img id="' . $img_tag . '" width="10" height="10" />';
      $expected_url_1    = '/front/document.send.php?docid=' . $doc_id_1 . '&tickets_id=' . $item->fields['id'];
      $expected_result_1 = '<a href="' . $expected_url_1 . '" target="_blank" ><img alt="' . $img_tag . '" width="10" src="' . $expected_url_1 . '" /></a>';
      $expected_url_2    = '/front/document.send.php?docid=' . $doc_id_2 . '&tickets_id=' . $item->fields['id'];
      $expected_result_2 = '<a href="' . $expected_url_2 . '" target="_blank" ><img alt="' . $img_tag . '" width="10" src="' . $expected_url_2 . '" /></a>';

      // Processed data is expected to be escaped
      $content_text = \Toolbox::addslashes_deep($content_text);
      $expected_result_1 = \Html::entities_deep($expected_result_1);
      $expected_result_2 = \Html::entities_deep($expected_result_2);

      $this->string(
         \Toolbox::convertTagToImage($content_text, $item, [$doc_id_1 => ['tag' => $img_tag]])
      )->isEqualTo($expected_result_1);

      $this->string(
         \Toolbox::convertTagToImage($content_text, $item, [$doc_id_2 => ['tag' => $img_tag]])
      )->isEqualTo($expected_result_2);
   }

   /**
    * Check conversion of tags to images when content contains multiple times same inlined image.
    */
   public function testConvertTagToImageWithDuplicatedInlinedImg() {

      $img_tag = uniqid('', true);

      $item = new \Ticket();
      $item->fields['id'] = mt_rand(1, 50);

      // Create multiple documents in DB
      $document = new \Document();
      $doc_id = $document->add([
         'name'     => 'img 1',
         'filename' => 'img.png',
         'mime'     => 'image/png',
         'tag'      => $img_tag,
      ]);
      $this->integer((int)$doc_id)->isGreaterThan(0);

      $content_text     = '<img id="' . $img_tag . '" width="10" height="10" />';
      $content_text    .= $content_text;
      $expected_url     = '/front/document.send.php?docid=' . $doc_id . '&tickets_id=' . $item->fields['id'];
      $expected_result  = '<a href="' . $expected_url . '" target="_blank" ><img alt="' . $img_tag . '" width="10" src="' . $expected_url . '" /></a>';
      $expected_result .= $expected_result;

      // Processed data is expected to be escaped
      $content_text = \Toolbox::addslashes_deep($content_text);
      $expected_result = \Html::entities_deep($expected_result);

      $this->string(
         \Toolbox::convertTagToImage($content_text, $item, [$doc_id => ['tag' => $img_tag]])
      )->isEqualTo($expected_result);
   }
}
